/****************************************************************************
 Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
 Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md).

 https://axmolengine.github.io/

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ****************************************************************************/

#include "UnitTest.h"
#include "ui/UIHelper.h"
#include "network/Uri.h"
#include "base/Utils.h"
#include "yasio/byte_buffer.hpp"

USING_NS_AX;
using namespace ax::network;

#if (AX_TARGET_PLATFORM == AX_PLATFORM_IOS)
#    if defined(__arm64__)
#        define USE_NEON64
#        define INCLUDE_NEON64
#    elif defined(__ARM_NEON__)
#        define USE_NEON32
#        define INCLUDE_NEON32
#    else
#    endif
#elif (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID)
#    if defined(__arm64__) || defined(__aarch64__)
#        define USE_NEON64
#        define INCLUDE_NEON64
#    elif defined(__ARM_NEON__)
#        define INCLUDE_NEON32
#    else
#    endif
#else

#endif

#if defined(__SSE__)
#    define USE_SSE
#    define INCLUDE_SSE
#endif

#if (defined INCLUDE_NEON64) || (defined INCLUDE_NEON32)  // FIXME: || (defined INCLUDE_SSE)
#    define UNIT_TEST_FOR_OPTIMIZED_MATH_UTIL
#endif

#define EXPECT_EQ(a, b) assert((a) == (b))
#define EXPECT_NE(a, b) assert((a) != (b))
#define EXPECT_TRUE(a) assert(a)
#define EXPECT_FALSE(a) assert(!(a))

// For ' < o > ' multiply test scene.

UnitTests::UnitTests()
{
    ADD_TEST_CASE(TemplateVectorTest);
    ADD_TEST_CASE(TemplateMapTest);
    ADD_TEST_CASE(ValueTest);
    ADD_TEST_CASE(UTFConversionTest);
    ADD_TEST_CASE(UIHelperSubStringTest);
    ADD_TEST_CASE(ParseIntegerListTest);
    ADD_TEST_CASE(ParseUriTest);
    ADD_TEST_CASE(ResizableBufferAdapterTest);
#ifdef UNIT_TEST_FOR_OPTIMIZED_MATH_UTIL
    ADD_TEST_CASE(MathUtilTest);
#endif
};

std::string UnitTestDemo::title() const
{
    return "UnitTest";
}

//---------------------------------------------------------------

void TemplateVectorTest::onEnter()
{
    UnitTestDemo::onEnter();

    Vector<Node*> vec;
    AXASSERT(vec.empty(), "vec should be empty.");
    AXASSERT(vec.capacity() == 0, "vec.capacity should be 0.");
    AXASSERT(vec.size() == 0, "vec.size should be 0.");
    AXASSERT(vec.max_size() > 0, "vec.max_size should > 0.");

    auto node1 = Node::create();
    node1->setTag(1);
    vec.pushBack(node1);
    AXASSERT(node1->getReferenceCount() == 2, "node1->getReferenceCount should be 2.");

    auto node2 = Node::create();
    node2->setTag(2);
    vec.pushBack(node2);
    AXASSERT(vec.getIndex(node1) == 0, "node1 should at index 0 in vec.");
    AXASSERT(vec.getIndex(node2) == 1, "node2 should at index 1 in vec.");

    auto node3 = Node::create();
    node3->setTag(3);
    vec.insert(1, node3);
    AXASSERT(vec.at(0)->getTag() == 1, "The element at 0, tag should be 1.");
    AXASSERT(vec.at(1)->getTag() == 3, "The element at 1, tag should be 3.");
    AXASSERT(vec.at(2)->getTag() == 2, "The element at 2, tag should be 2.");

    // Test copy constructor
    Vector<Node*> vec2(vec);
    AXASSERT(vec2.size() == vec.size(), "vec2 and vec should have equal size.");
    ssize_t size = vec.size();
    for (ssize_t i = 0; i < size; ++i)
    {
        AXASSERT(vec2.at(i) == vec.at(i), "The element at the same index in vec2 and vec2 should be equal.");
        AXASSERT(vec.at(i)->getReferenceCount() == 3, "The reference count of element in vec is 3. ");
        AXASSERT(vec2.at(i)->getReferenceCount() == 3, "The reference count of element in vec2 is 3. ");
    }

    // Test copy assignment operator
    Vector<Node*> vec3;
    vec3 = vec2;
    AXASSERT(vec3.size() == vec2.size(), "vec3 and vec2 should have equal size.");
    size = vec3.size();
    for (ssize_t i = 0; i < size; ++i)
    {
        AXASSERT(vec3.at(i) == vec2.at(i), "The element at the same index in vec3 and vec2 should be equal.");
        AXASSERT(vec3.at(i)->getReferenceCount() == 4, "The reference count of element in vec3 is 4. ");
        AXASSERT(vec2.at(i)->getReferenceCount() == 4, "The reference count of element in vec2 is 4. ");
        AXASSERT(vec.at(i)->getReferenceCount() == 4, "The reference count of element in vec is 4. ");
    }

    // Test move constructor

    auto createVector = []() {
        Vector<Node*> ret;

        for (int i = 0; i < 20; i++)
        {
            ret.pushBack(Node::create());
        }

        int j = 1000;
        for (auto&& child : ret)
        {
            child->setTag(j++);
        }

        return ret;
    };

    Vector<Node*> vec4(createVector());
    for (const auto& child : vec4)
    {
        AX_UNUSED_PARAM(child);
        AXASSERT(child->getReferenceCount() == 2, "child's reference count should be 2.");
    }

    // Test init Vector<T> with capacity
    Vector<Node*> vec5(10);
    AXASSERT(vec5.capacity() == 10, "vec5's capacity should be 10.");
    vec5.reserve(20);
    AXASSERT(vec5.capacity() == 20, "vec5's capacity should be 20.");

    AXASSERT(vec5.size() == 0, "vec5's size should be 0.");
    AXASSERT(vec5.empty(), "vec5 is empty now.");

    auto toRemovedNode = Node::create();
    vec5.pushBack(toRemovedNode);
    AXASSERT(toRemovedNode->getReferenceCount() == 2, "toRemovedNode's reference count is 2.");

    // Test move assignment operator
    vec5 = createVector();
    AXASSERT(toRemovedNode->getReferenceCount() == 1, "toRemovedNode's reference count is 1.");
    AXASSERT(vec5.size() == 20, "size should be 20");

    for (const auto& child : vec5)
    {
        AX_UNUSED_PARAM(child);
        AXASSERT(child->getReferenceCount() == 2, "child's reference count is 2.");
    }

    // Test Vector<T>::find
    AXASSERT(vec.find(node3) == (vec.begin() + 1), "node3 is the 2nd element in vec.");
    AXASSERT(std::find(std::begin(vec), std::end(vec), node2) == (vec.begin() + 2), "node2 is the 3rd element in vec.");

    AXASSERT(vec.front()->getTag() == 1, "vec's front element's tag is 1.");
    AXASSERT(vec.back()->getTag() == 2, "vec's back element's tag is 2.");

    AXASSERT(vec.getRandomObject(), "vec getRandomObject should return true.");
    AXASSERT(!vec.contains(Node::create()), "vec doesn't contain a empty Node instance.");
    AXASSERT(vec.contains(node1), "vec contains node1.");
    AXASSERT(vec.contains(node2), "vec contains node2.");
    AXASSERT(vec.contains(node3), "vec contains node3.");
    AXASSERT(vec.equals(vec2), "vec is equal to vec2.");
    AXASSERT(vec.equals(vec3), "vec is equal to vec3.");

    // Insert
    vec5.insert(2, node1);
    AXASSERT(vec5.at(2)->getTag() == 1, "vec5's 3rd element's tag is 1.");
    AXASSERT(vec5.size() == 21, "vec5's size is 21.");
    vec5.back()->setTag(100);
    vec5.popBack();
    AXASSERT(vec5.size() == 20, "vec5's size is 20.");
    AXASSERT(vec5.back()->getTag() != 100, "the back element of vec5's tag is 100.");

    // Erase and clear
    Vector<Node*> vec6 = createVector();
    Vector<Node*> vec7 = vec6;  // Copy for check

    AXASSERT(vec6.size() == 20, "vec6's size is 20.");
    vec6.erase(vec6.begin() + 1);  //
    AXASSERT(vec6.size() == 19, "vec6's size is 19.");
    AXASSERT((*(vec6.begin() + 1))->getTag() == 1002, "The 2rd element in vec6's tag is 1002.");
    vec6.erase(vec6.begin() + 2, vec6.begin() + 10);
    AXASSERT(vec6.size() == 11, "vec6's size is 11.");
    AXASSERT(vec6.at(0)->getTag() == 1000, "vec6's first element's tag is 1000.");
    AXASSERT(vec6.at(1)->getTag() == 1002, "vec6's second element's tag is 1002.");
    AXASSERT(vec6.at(2)->getTag() == 1011, "vec6's third element's tag is 1011.");
    AXASSERT(vec6.at(3)->getTag() == 1012, "vec6's fouth element's tag is 1012.");
    vec6.erase(3);
    AXASSERT(vec6.at(3)->getTag() == 1013, "vec6's 4th element's tag is 1013.");
    vec6.eraseObject(vec6.at(2));
    AXASSERT(vec6.at(2)->getTag() == 1013, "vec6's 3rd element's tag is 1013.");
    vec6.clear();

    auto objA = Node::create();  // retain count is 1
    auto objB = Node::create();
    auto objC = Node::create();
    {
        Vector<Node*> array1;
        Vector<Node*> array2;

        // push back objA 3 times
        array1.pushBack(objA);  // retain count is 2
        array1.pushBack(objA);  // retain count is 3
        array1.pushBack(objA);  // retain count is 4

        array2.pushBack(objA);  // retain count is 5
        array2.pushBack(objB);
        array2.pushBack(objC);

        for (auto&& obj : array1)
        {
            array2.eraseObject(obj);
        }
        AXASSERT(objA->getReferenceCount() == 4, "objA's reference count is 4.");
    }
    AXASSERT(objA->getReferenceCount() == 1, "objA's reference count is 1.");

    {
        Vector<Node*> array1;
        // push back objA 3 times
        array1.pushBack(objA);  // retain count is 2
        array1.pushBack(objA);  // retain count is 3
        array1.pushBack(objA);  // retain count is 4
        AXASSERT(objA->getReferenceCount() == 4, "objA's reference count is 4.");
        array1.eraseObject(objA, true);  // Remove all occurrences in the Vector.
        AXASSERT(objA->getReferenceCount() == 1, "objA's reference count is 1.");

        array1.pushBack(objA);  // retain count is 2
        array1.pushBack(objA);  // retain count is 3
        array1.pushBack(objA);  // retain count is 4

        array1.eraseObject(objA, false);
        AXASSERT(objA->getReferenceCount() == 3,
                 "objA's reference count is 3.");  // Only remove the first occurrence in the Vector.
    }

    // Check the retain count in vec7
    AXASSERT(vec7.size() == 20, "vec7's size is 20.");
    for (const auto& child : vec7)
    {
        AX_UNUSED_PARAM(child);
        AXASSERT(child->getReferenceCount() == 2, "child's reference count is 2.");
    }

    // Sort
    Vector<Node*> vecForSort = createVector();
    std::sort(vecForSort.begin(), vecForSort.end(), [](Node* a, Node* b) { return a->getTag() >= b->getTag(); });

    for (int i = 0; i < 20; ++i)
    {
        AXASSERT(vecForSort.at(i)->getTag() - 1000 == (19 - i), "vecForSort's element's tag is invalid.");
    }

    // Reverse
    vecForSort.reverse();
    for (int i = 0; i < 20; ++i)
    {
        AXASSERT(vecForSort.at(i)->getTag() - 1000 == i, "vecForSort's element's tag is invalid.");
    }

    // Swap
    Vector<Node*> vecForSwap = createVector();
    vecForSwap.swap(2, 4);
    AXASSERT(vecForSwap.at(2)->getTag() == 1004, "vecForSwap's 3nd element's tag is 1004.");
    AXASSERT(vecForSwap.at(4)->getTag() == 1002, "vecForSwap's 5rd element's tag is 1002.");
    vecForSwap.swap(vecForSwap.at(2), vecForSwap.at(4));
    AXASSERT(vecForSwap.at(2)->getTag() == 1002, "vecForSwap's 3rd element's tag is 1002.");
    AXASSERT(vecForSwap.at(4)->getTag() == 1004, "vecForSwap's 5rd element's tag is 1004.");

    // shrinkToFit
    Vector<Node*> vecForShrink = createVector();
    vecForShrink.reserve(100);
    AXASSERT(vecForShrink.capacity() == 100, "vecForShrink's capacity is 100.");
    vecForShrink.pushBack(Node::create());
    vecForShrink.shrinkToFit();
    AXASSERT(vecForShrink.capacity() == 21, "vecForShrink's capacity is 21.");

    // get random object
    // Set the seed by time
    std::srand((unsigned)time(nullptr));
    Vector<Node*> vecForRandom = createVector();
    ax::print("<--- begin ---->");
    for (int i = 0; i < vecForRandom.size(); ++i)
    {
        ax::print("Vector: random object tag = %d", vecForRandom.getRandomObject()->getTag());
    }
    ax::print("<---- end  ---->");

    // Self assignment
    Vector<Node*> vecSelfAssign = createVector();
    vecSelfAssign               = vecSelfAssign;
    AXASSERT(vecSelfAssign.size() == 20, "vecSelfAssign's size is 20.");

    for (const auto& child : vecSelfAssign)
    {
        AX_UNUSED_PARAM(child);
        AXASSERT(child->getReferenceCount() == 2, "child's reference count is 2.");
    }

    vecSelfAssign = std::move(vecSelfAssign);
    AXASSERT(vecSelfAssign.size() == 20, "vecSelfAssign's size is 20.");

    for (const auto& child : vecSelfAssign)
    {
        AX_UNUSED_PARAM(child);
        AXASSERT(child->getReferenceCount() == 2, "child's reference count is 2.");
    }

    // const at
    Vector<Node*> vecConstAt = createVector();
    constFunc(vecConstAt);
}

void TemplateVectorTest::constFunc(const Vector<Node*>& vec) const
{
    ax::print("vec[8] = %d", vec.at(8)->getTag());
}

std::string TemplateVectorTest::subtitle() const
{
    return "Vector<T>, should not crash";
}

//---------------------------------------------------------------

void TemplateMapTest::onEnter()
{
    UnitTestDemo::onEnter();

    auto createMap = []() {
        StringMap<Node*> ret;
        for (int i = 0; i < 20; ++i)
        {
            auto node = Node::create();
            node->setTag(1000 + i);
            ret.insert(StringUtils::toString(i), node);
        }

        return ret;
    };

    // Default constructor
    Map<std::string, Node*> map1;
    AXASSERT(map1.empty(), "map1 is empty.");
    AXASSERT(map1.size() == 0, "map1's size is 0.");
    AXASSERT(map1.keys().empty(), "map1's keys are empty.");
    AXASSERT(map1.keys(Node::create()).empty(), "map1's keys don't contain a empty Node.");

    // Move constructor
    auto map2 = createMap();
    for (const auto& e : map2)
    {
        AX_UNUSED_PARAM(e);
        AXASSERT(e.second->getReferenceCount() == 2, "e.second element's reference count is 2.");
    }

    // Copy constructor
    auto map3(map2);
    for (const auto& e : map3)
    {
        AX_UNUSED_PARAM(e);
        AXASSERT(e.second->getReferenceCount() == 3, "e.second's reference count is 3.");
    }

    // Move assignment operator
    StringMap<Node*> map4;
    auto unusedNode = Node::create();
    map4.insert("unused", unusedNode);
    map4 = createMap();
    AXASSERT(unusedNode->getReferenceCount() == 1, "unusedNode's reference count is 1.");
    for (const auto& e : map4)
    {
        AX_UNUSED_PARAM(e);
        AXASSERT(e.second->getReferenceCount() == 2, "e.second's reference count is 2.");
    }

    // Copy assignment operator
    StringMap<Node*> map5;
    map5 = map4;
    for (const auto& e : map5)
    {
        AX_UNUSED_PARAM(e);
        AXASSERT(e.second->getReferenceCount() == 3, "e.second's reference count is 3.");
    }

    // Check size
    AXASSERT(map4.size() == map5.size(), "map4's size is equal to map5.size.");

    for (const auto& e : map4)
    {
        AX_UNUSED_PARAM(e);
        AXASSERT(e.second == map5.find(e.first)->second, "e.second can't be found in map5.");
    }

    // bucket_count, bucket_size(n), bucket
    ax::print("--------------");
    ax::print("bucket_count = %d", static_cast<int>(map4.bucketCount()));
    ax::print("size = %d", static_cast<int>(map4.size()));
    for (int i = 0; i < map4.bucketCount(); ++i)
    {
        ax::print("bucket_size(%d) = %d", i, static_cast<int>(map4.bucketSize(i)));
    }
    for (const auto& e : map4)
    {
        ax::print("bucket(\"%s\"), bucket index = %d", e.first.c_str(), static_cast<int>(map4.bucket(e.first)));
    }

    ax::print("----- all keys---------");

    // keys and at
    auto keys = map4.keys();
    for (const auto& key : keys)
    {
        ax::print("key = %s", key.c_str());
    }

    auto node10Key = map4.at("10");
    map4.insert("100", node10Key);
    map4.insert("101", node10Key);
    map4.insert("102", node10Key);

    ax::print("------ keys for object --------");
    auto keysForObject = map4.keys(node10Key);
    for (const auto& key : keysForObject)
    {
        ax::print("key = %s", key.c_str());
    }
    ax::print("--------------");

    // at in const function
    constFunc(map4);

    // find
    auto nodeToFind = map4.find("10");
    AX_UNUSED_PARAM(nodeToFind);
    AXASSERT(nodeToFind->second->getTag() == 1010, "nodeToFind's tag value is 1010.");

    // insert
    Map<std::string, Node*> map6;
    auto node1 = Node::create();
    node1->setTag(101);
    auto node2 = Node::create();
    node2->setTag(102);
    auto node3 = Node::create();
    node3->setTag(103);
    map6.insert("insert01", node1);
    map6.insert("insert02", node2);
    map6.insert("insert03", node3);

    AXASSERT(node1->getReferenceCount() == 2, "node1's reference count is 2.");
    AXASSERT(node2->getReferenceCount() == 2, "node2's reference count is 2.");
    AXASSERT(node3->getReferenceCount() == 2, "node3's reference count is 2.");
    AXASSERT(map6.at("insert01") == node1, "The element at insert01 is equal to node1.");
    AXASSERT(map6.at("insert02") == node2, "The element at insert02 is equal to node2.");
    AXASSERT(map6.at("insert03") == node3, "The element at insert03 is equal to node3.");

    // erase
    StringMap<Node*> mapForErase = createMap();
    mapForErase.erase(mapForErase.find("9"));
    AXASSERT(mapForErase.find("9") == mapForErase.end(), "9 is already removed.");
    AXASSERT(mapForErase.size() == 19, "mapForErase's size is 19.");

    mapForErase.erase("7");
    AXASSERT(mapForErase.find("7") == mapForErase.end(), "7 is already removed.");
    AXASSERT(mapForErase.size() == 18, "mapForErase's size is 18.");

    std::vector<std::string> itemsToRemove;
    itemsToRemove.emplace_back("2");
    itemsToRemove.emplace_back("3");
    itemsToRemove.emplace_back("4");
    mapForErase.erase(itemsToRemove);
    AXASSERT(mapForErase.size() == 15, "mapForErase's size is 15.");

    // clear
    StringMap<Node*> mapForClear = createMap();
    auto mapForClearCopy         = mapForClear;
    mapForClear.clear();

    for (const auto& e : mapForClearCopy)
    {
        AX_UNUSED_PARAM(e);
        AXASSERT(e.second->getReferenceCount() == 2, "e.second's reference count is 2.");
    }

    // get random object
    // Set the seed by time
    std::srand((unsigned)time(nullptr));
    StringMap<Node*> mapForRandom = createMap();
    ax::print("<--- begin ---->");
    for (int i = 0; i < mapForRandom.size(); ++i)
    {
        ax::print("Map: random object tag = %d", mapForRandom.getRandomObject()->getTag());
    }
    ax::print("<---- end  ---->");

    // Self assignment
    StringMap<Node*> mapForSelfAssign = createMap();
    mapForSelfAssign                  = mapForSelfAssign;
    AXASSERT(mapForSelfAssign.size() == 20, "mapForSelfAssign's size is 20.");

    for (const auto& e : mapForSelfAssign)
    {
        AX_UNUSED_PARAM(e);
        AXASSERT(e.second->getReferenceCount() == 2, "e.second's reference count is 2.");
    }

    mapForSelfAssign = std::move(mapForSelfAssign);
    AXASSERT(mapForSelfAssign.size() == 20, "mapForSelfAssign's size is 20.");

    for (const auto& e : mapForSelfAssign)
    {
        AX_UNUSED_PARAM(e);
        AXASSERT(e.second->getReferenceCount() == 2, "e.second's reference's count is 2.");
    }
}

void TemplateMapTest::constFunc(const StringMap<Node*>& map) const
{
    ax::print("[%s]=(tag)%d", "0", map.at("0")->getTag());
    ax::print("[%s]=(tag)%d", "1", map.find("1")->second->getTag());
}

std::string TemplateMapTest::subtitle() const
{
    return "Map<K, V>, should not crash";
}

//----------------------------------

void ValueTest::onEnter()
{
    UnitTestDemo::onEnter();

    Value v1;
    AXASSERT(v1.getType() == Value::Type::NONE, "v1's value type should be VALUE::Type::NONE.");
    AXASSERT(v1.isNull(), "v1 is null.");

    Value v2(100);
    AXASSERT(v2.getType() == Value::Type::INTEGER, "v2's value type should be VALUE::Type::INTEGER.");
    AXASSERT(!v2.isNull(), "v2 is not null.");

    Value v3(101.4f);
    AXASSERT(v3.getType() == Value::Type::FLOAT, "v3's value type should be VALUE::Type::FLOAT.");
    AXASSERT(!v3.isNull(), "v3 is not null.");

    Value v4(106.1);
    AXASSERT(v4.getType() == Value::Type::DOUBLE, "v4's value type should be VALUE::Type::DOUBLE.");
    AXASSERT(!v4.isNull(), "v4 is not null.");

    unsigned char byte = 50;
    Value v5(byte);
    AXASSERT(v5.getType() == Value::Type::INT_UI32, "v5's value type should be Value::Type::INT_UI32.");
    AXASSERT(!v5.isNull(), "v5 is not null.");

    Value v6(true);
    AXASSERT(v6.getType() == Value::Type::BOOLEAN, "v6's value type is Value::Type::BOOLEAN.");
    AXASSERT(!v6.isNull(), "v6 is not null.");

    Value v7("string");
    AXASSERT(v7.getType() == Value::Type::STRING, "v7's value type is Value::type::STRING.");
    AXASSERT(!v7.isNull(), "v7 is not null.");

    Value v8(std::string("string2"));
    AXASSERT(v8.getType() == Value::Type::STRING, "v8's value type is Value::Type::STRING.");
    AXASSERT(!v8.isNull(), "v8 is not null.");

    auto createValueVector = [&]() {
        ValueVector ret;
        ret.emplace_back(v1);
        ret.emplace_back(v2);
        ret.emplace_back(v3);
        return ret;
    };

    Value v9(createValueVector());
    AXASSERT(v9.getType() == Value::Type::VECTOR, "v9's value type is Value::Type::VECTOR.");
    AXASSERT(!v9.isNull(), "v9 is not null.");

    auto createValueMap = [&]() {
        ValueMap ret;
        ret["aaa"] = v1;
        ret["bbb"] = v2;
        ret["ccc"] = v3;
        return ret;
    };

    Value v10(createValueMap());
    AXASSERT(v10.getType() == Value::Type::MAP, "v10's value type is Value::Type::MAP.");
    AXASSERT(!v10.isNull(), "v10 is not null.");

    auto createValueMapIntKey = [&]() {
        ValueMapIntKey ret;
        ret[111] = v1;
        ret[222] = v2;
        ret[333] = v3;
        return ret;
    };

    Value v11(createValueMapIntKey());
    AXASSERT(v11.getType() == Value::Type::INT_KEY_MAP, "v11's value type is Value::Type::INT_KEY_MAP.");
    AXASSERT(!v11.isNull(), "v11 is not null.");
}

std::string ValueTest::subtitle() const
{
    return "Value Test, should not crash";
}

void ValueTest::constFunc(const Value& /*value*/) const {}

// UTFConversionTest

// FIXME: made as define to prevent compile warnings in release mode. Better is to be a `const static int`
#define TEST_CODE_NUM 11

static const char16_t __utf16Code[] = {
    0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x0041, 0x0000,
};

// to avoid Xcode error, char => unsigned char
// If you use this table, please cast manually as (const char *).
static const unsigned char __utf8Code[] = {
    0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84, 0xE3, 0x81, 0x86, 0xE3, 0x81, 0x88, 0xE3, 0x81, 0x8A, 0xE3,
    0x81, 0x82, 0xE3, 0x81, 0x84, 0xE3, 0x81, 0x86, 0xE3, 0x81, 0x88, 0xE3, 0x81, 0x8A, 0x41, 0x00,
};

static const char16_t WHITE_SPACE_CODE[] = {0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x0020, 0x0085, 0x00A0, 0x1680,
                                            0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008,
                                            0x2009, 0x200A, 0x2028, 0x2029, 0x202F, 0x205F, 0x3000};

static void doUTFConversion()
{
    bool isSuccess = false;

    std::string originalUTF8     = (const char*)__utf8Code;
    std::u16string originalUTF16 = __utf16Code;

    //---------------------------
    std::string utf8Str;
    isSuccess = StringUtils::UTF16ToUTF8(originalUTF16, utf8Str);

    if (isSuccess)
    {
        isSuccess = memcmp(utf8Str.data(), originalUTF8.data(), originalUTF8.length() + 1) == 0;
    }

    AXASSERT(isSuccess, "StringUtils::UTF16ToUTF8 failed");

    //---------------------------
    std::u16string utf16Str;
    isSuccess = StringUtils::UTF8ToUTF16(originalUTF8, utf16Str);

    if (isSuccess)
    {
        isSuccess = memcmp(utf16Str.data(), originalUTF16.data(), originalUTF16.length() + 1) == 0;
    }

    AXASSERT(isSuccess && (utf16Str.length() == TEST_CODE_NUM), "StringUtils::UTF8ToUTF16 failed");

    //---------------------------
    auto vec1 = StringUtils::getChar16VectorFromUTF16String(originalUTF16);

    AXASSERT(vec1.size() == originalUTF16.length(), "StringUtils::getChar16VectorFromUTF16String failed");

    //---------------------------
    std::vector<char16_t> vec2(vec1);
    vec2.emplace_back(0x2009);
    vec2.emplace_back(0x2009);
    vec2.emplace_back(0x2009);
    vec2.emplace_back(0x2009);

    std::vector<char16_t> vec3(vec2);
    StringUtils::trimUTF16Vector(vec2);

    AXASSERT(vec1.size() == vec2.size(), "StringUtils::trimUTF16Vector failed");

    for (size_t i = 0; i < vec2.size(); i++)
    {
        AXASSERT(vec1.at(i) == vec2.at(i), "StringUtils::trimUTF16Vector failed");
    }

    //---------------------------
    AXASSERT(StringUtils::getCharacterCountInUTF8String(originalUTF8) == TEST_CODE_NUM,
             "StringUtils::getCharacterCountInUTF8String failed");

    //---------------------------
    AXASSERT(StringUtils::getIndexOfLastNotChar16(vec3, 0x2009) == (vec1.size() - 1),
             "StringUtils::getIndexOfLastNotChar16 failed");

    //---------------------------
    AXASSERT(originalUTF16.length() == TEST_CODE_NUM,
             "The length of the original utf16 string isn't equal to TEST_CODE_NUM");

    //---------------------------
    size_t whiteCodeNum = sizeof(WHITE_SPACE_CODE) / sizeof(WHITE_SPACE_CODE[0]);
    for (size_t i = 0; i < whiteCodeNum; i++)
    {
        AXASSERT(StringUtils::isUnicodeSpace(WHITE_SPACE_CODE[i]), "StringUtils::isUnicodeSpace failed");
    }

    AXASSERT(!StringUtils::isUnicodeSpace(0xFFFF), "StringUtils::isUnicodeSpace failed");

    AXASSERT(!StringUtils::isCJKUnicode(0xFFFF) && StringUtils::isCJKUnicode(0x3100),
             "StringUtils::isCJKUnicode failed");
}

void UTFConversionTest::onEnter()
{
    UnitTestDemo::onEnter();

    for (int i = 0; i < 10000; ++i)
    {
        doUTFConversion();
    }
}

std::string UTFConversionTest::subtitle() const
{
    return "UTF8 <-> UTF16 Conversion Test, no crash";
}

// UIHelperSubStringTest

void UIHelperSubStringTest::onEnter()
{
    UnitTestDemo::onEnter();

    using ax::ui::Helper;
    {
        // Trivial case
        std::string source = "abcdefghij";
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 2) == "ab");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 2, 2) == "cd");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 4, 2) == "ef");
    }
    {
        // Empty string
        std::string source = "";

        // OK
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 1) == "");

        // Error: These cases cause "out of range" error
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 1, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 1, 1) == "");
    }
    {
        // Ascii
        std::string source = "abc";

        // OK
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 1, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 2, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 3, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 3) == "abc");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 4) == "abc");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 1, 2) == "bc");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 1, 3) == "bc");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 2, 1) == "c");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 2, 2) == "c");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 3, 1) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 3, 2) == "");

        // Error: These cases cause "out of range" error
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 4, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 4, 1) == "");
    }
    {
        // CJK characters
        std::string source = "这里是中文测试例";

        // OK
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 1, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 7, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 8, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 8, 1) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 1) == "\xe8\xbf\x99");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 4) == "\xe8\xbf\x99\xe9\x87\x8c\xe6\x98\xaf\xe4\xb8\xad");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 8) ==
                  "\xe8\xbf\x99\xe9\x87\x8c\xe6\x98\xaf\xe4\xb8\xad\xe6\x96\x87\xe6\xb5\x8b\xe8\xaf\x95\xe4\xbe\x8b");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 100) ==
                  "\xe8\xbf\x99\xe9\x87\x8c\xe6\x98\xaf\xe4\xb8\xad\xe6\x96\x87\xe6\xb5\x8b\xe8\xaf\x95\xe4\xbe\x8b");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 2, 5) ==
                  "\xe6\x98\xaf\xe4\xb8\xad\xe6\x96\x87\xe6\xb5\x8b\xe8\xaf\x95");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 6, 2) == "\xe8\xaf\x95\xe4\xbe\x8b");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 6, 100) == "\xe8\xaf\x95\xe4\xbe\x8b");

        // Error: These cases cause "out of range" error
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 9, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 9, 1) == "");
    }
    {
        // Redundant UTF-8 sequence for Directory traversal attack (1)
        std::string source = "\xC0\xAF";

        // Error: Can't convert string to correct encoding such as UTF-32
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 1) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 1, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 1, 1) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 2) == "");
    }
    {
        // Redundant UTF-8 sequence for Directory traversal attack (2)
        std::string source = "\xE0\x80\xAF";

        // Error: Can't convert string to correct encoding such as UTF-32
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 1) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 1, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 1, 1) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 3) == "");
    }
    {
        // Redundant UTF-8 sequence for Directory traversal attack (3)
        std::string source = "\xF0\x80\x80\xAF";

        // Error: Can't convert string to correct encoding such as UTF-32
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 1) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 1, 0) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 1, 1) == "");
        AX_ASSERT(Helper::getSubStringOfUTF8String(source, 0, 4) == "");
    }
}

std::string UIHelperSubStringTest::subtitle() const
{
    return "ui::Helper::getSubStringOfUTF8String Test";
}

// ParseIntegerListTest
void ParseIntegerListTest::onEnter()
{
    UnitTestDemo::onEnter();

    {
        using ax::utils::parseIntegerList;

        std::vector<int> res1{};
        EXPECT_EQ(res1, parseIntegerList(""));

        std::vector<int> res2{1};
        EXPECT_EQ(res2, parseIntegerList("1"));

        std::vector<int> res3{1, 2};
        EXPECT_EQ(res3, parseIntegerList("1 2"));

        std::vector<int> res4{2, 4, 3, 1, 4, 2, 0, 4, 1, 0, 4, 5};
        EXPECT_EQ(res4, parseIntegerList("2 4 3 1 4 2 0 4 1 0 4 5"));

        std::vector<int> res5{73, 48, 57, 117, 27, 117, 29, 77, 14, 62, 26, 7, 55, 2};
        EXPECT_EQ(res5, parseIntegerList("73 48 57 117 27 117 29 77 14 62 26 7 55 2"));
    }
}

std::string ParseIntegerListTest::subtitle() const
{
    return "utils::parseIntegerList Test";
}

// ParseUriTest
void ParseUriTest::onEnter()
{
    UnitTestDemo::onEnter();

    {
        std::string s("http://www.facebook.com/hello/world?query#fragment");
        Uri u = Uri::parse(s);
        EXPECT_EQ("http", u.getScheme());
        EXPECT_EQ("", u.getUserName());
        EXPECT_EQ("", u.getPassword());
        EXPECT_EQ("www.facebook.com", u.getHost());
        EXPECT_EQ(80, u.getPort());
        EXPECT_EQ("www.facebook.com", u.getAuthority());
        EXPECT_EQ("/hello/world", u.getPath());
        EXPECT_EQ("query", u.getQuery());
        EXPECT_EQ("fragment", u.getFragment());
        EXPECT_EQ(s, u.toString());  // canonical
    }

    {
        std::string s("http://www.facebook.com:8080/hello/world?query#fragment");
        Uri u = Uri::parse(s);
        EXPECT_EQ("http", u.getScheme());
        EXPECT_EQ("", u.getUserName());
        EXPECT_EQ("", u.getPassword());
        EXPECT_EQ("www.facebook.com", u.getHost());
        EXPECT_EQ(8080, u.getPort());
        EXPECT_EQ("www.facebook.com:8080", u.getAuthority());
        EXPECT_EQ("/hello/world", u.getPath());
        EXPECT_EQ("query", u.getQuery());
        EXPECT_EQ("fragment", u.getFragment());
        EXPECT_EQ(s, u.toString());  // canonical
    }

    {
        std::string s("http://127.0.0.1:8080/hello/world?query#fragment");
        Uri u = Uri::parse(s);
        EXPECT_EQ("http", u.getScheme());
        EXPECT_EQ("", u.getUserName());
        EXPECT_EQ("", u.getPassword());
        EXPECT_EQ("127.0.0.1", u.getHost());
        EXPECT_EQ(8080, u.getPort());
        EXPECT_EQ("127.0.0.1:8080", u.getAuthority());
        EXPECT_EQ("/hello/world", u.getPath());
        EXPECT_EQ("query", u.getQuery());
        EXPECT_EQ("fragment", u.getFragment());
        EXPECT_EQ(s, u.toString());  // canonical
    }

    {
        std::string s("http://[::1]:8080/hello/world?query#fragment");
        Uri u = Uri::parse(s);
        EXPECT_EQ("http", u.getScheme());
        EXPECT_EQ("", u.getUserName());
        EXPECT_EQ("", u.getPassword());
        EXPECT_EQ("[::1]", u.getHost());
        EXPECT_EQ("::1", u.getHostName());
        EXPECT_EQ(8080, u.getPort());
        EXPECT_EQ("[::1]:8080", u.getAuthority());
        EXPECT_EQ("/hello/world", u.getPath());
        EXPECT_EQ("query", u.getQuery());
        EXPECT_EQ("fragment", u.getFragment());
        EXPECT_EQ(s, u.toString());  // canonical
    }

    {
        std::string s("http://[2401:db00:20:7004:face:0:29:0]:8080/hello/world?query");
        Uri u = Uri::parse(s);
        EXPECT_EQ("http", u.getScheme());
        EXPECT_EQ("", u.getUserName());
        EXPECT_EQ("", u.getPassword());
        EXPECT_EQ("[2401:db00:20:7004:face:0:29:0]", u.getHost());
        EXPECT_EQ("2401:db00:20:7004:face:0:29:0", u.getHostName());
        EXPECT_EQ(8080, u.getPort());
        EXPECT_EQ("[2401:db00:20:7004:face:0:29:0]:8080", u.getAuthority());
        EXPECT_EQ("/hello/world", u.getPath());
        EXPECT_EQ("query", u.getQuery());
        EXPECT_EQ("", u.getFragment());
        EXPECT_EQ(s, u.toString());  // canonical
    }

    {
        std::string s("http://[2401:db00:20:7004:face:0:29:0]/hello/world?query");
        Uri u = Uri::parse(s);
        EXPECT_EQ("http", u.getScheme());
        EXPECT_EQ("", u.getUserName());
        EXPECT_EQ("", u.getPassword());
        EXPECT_EQ("[2401:db00:20:7004:face:0:29:0]", u.getHost());
        EXPECT_EQ("2401:db00:20:7004:face:0:29:0", u.getHostName());
        EXPECT_EQ(80, u.getPort());
        EXPECT_EQ("[2401:db00:20:7004:face:0:29:0]", u.getAuthority());
        EXPECT_EQ("/hello/world", u.getPath());
        EXPECT_EQ("query", u.getQuery());
        EXPECT_EQ("", u.getFragment());
        EXPECT_EQ(s, u.toString());  // canonical
    }

    {
        std::string s("http://user:pass@host.com/");
        Uri u = Uri::parse(s);
        EXPECT_EQ("http", u.getScheme());
        EXPECT_EQ("user", u.getUserName());
        EXPECT_EQ("pass", u.getPassword());
        EXPECT_EQ("host.com", u.getHost());
        EXPECT_EQ(80, u.getPort());
        EXPECT_EQ("user:pass@host.com", u.getAuthority());
        EXPECT_EQ("/", u.getPath());
        EXPECT_EQ("", u.getQuery());
        EXPECT_EQ("", u.getFragment());
        EXPECT_EQ(s, u.toString());
    }

    {
        std::string s("http://user@host.com/");
        Uri u = Uri::parse(s);
        EXPECT_EQ("http", u.getScheme());
        EXPECT_EQ("user", u.getUserName());
        EXPECT_EQ("", u.getPassword());
        EXPECT_EQ("host.com", u.getHost());
        EXPECT_EQ(80, u.getPort());
        EXPECT_EQ("user@host.com", u.getAuthority());
        EXPECT_EQ("/", u.getPath());
        EXPECT_EQ("", u.getQuery());
        EXPECT_EQ("", u.getFragment());
        EXPECT_EQ(s, u.toString());
    }

    {
        std::string s("http://user:@host.com/");
        Uri u = Uri::parse(s);
        EXPECT_EQ("http", u.getScheme());
        EXPECT_EQ("user", u.getUserName());
        EXPECT_EQ("", u.getPassword());
        EXPECT_EQ("host.com", u.getHost());
        EXPECT_EQ(80, u.getPort());
        EXPECT_EQ("user@host.com", u.getAuthority());
        EXPECT_EQ("/", u.getPath());
        EXPECT_EQ("", u.getQuery());
        EXPECT_EQ("", u.getFragment());
        EXPECT_EQ("http://user@host.com/", u.toString());
    }

    {
        std::string s("http://:pass@host.com/");
        Uri u = Uri::parse(s);
        EXPECT_EQ("http", u.getScheme());
        EXPECT_EQ("", u.getUserName());
        EXPECT_EQ("pass", u.getPassword());
        EXPECT_EQ("host.com", u.getHost());
        EXPECT_EQ(80, u.getPort());
        EXPECT_EQ(":pass@host.com", u.getAuthority());
        EXPECT_EQ("/", u.getPath());
        EXPECT_EQ("", u.getQuery());
        EXPECT_EQ("", u.getFragment());
        EXPECT_EQ(s, u.toString());
    }

    {
        std::string s("http://@host.com/");
        Uri u = Uri::parse(s);
        EXPECT_EQ("http", u.getScheme());
        EXPECT_EQ("", u.getUserName());
        EXPECT_EQ("", u.getPassword());
        EXPECT_EQ("host.com", u.getHost());
        EXPECT_EQ(80, u.getPort());
        EXPECT_EQ("host.com", u.getAuthority());
        EXPECT_EQ("/", u.getPath());
        EXPECT_EQ("", u.getQuery());
        EXPECT_EQ("", u.getFragment());
        EXPECT_EQ("http://host.com/", u.toString());
    }

    {
        std::string s("http://:@host.com/");
        Uri u = Uri::parse(s);
        EXPECT_EQ("http", u.getScheme());
        EXPECT_EQ("", u.getUserName());
        EXPECT_EQ("", u.getPassword());
        EXPECT_EQ("host.com", u.getHost());
        EXPECT_EQ(80, u.getPort());
        EXPECT_EQ("host.com", u.getAuthority());
        EXPECT_EQ("/", u.getPath());
        EXPECT_EQ("", u.getQuery());
        EXPECT_EQ("", u.getFragment());
        EXPECT_EQ("http://host.com/", u.toString());
    }

    {
        std::string s("file:///etc/motd");
        Uri u = Uri::parse(s);
        EXPECT_EQ("file", u.getScheme());
        EXPECT_EQ("", u.getUserName());
        EXPECT_EQ("", u.getPassword());
        EXPECT_EQ("", u.getHost());
        EXPECT_EQ(0, u.getPort());
        EXPECT_EQ("", u.getAuthority());
        EXPECT_EQ("/etc/motd", u.getPath());
        EXPECT_EQ("", u.getQuery());
        EXPECT_EQ("", u.getFragment());
        EXPECT_EQ(s, u.toString());
    }

    {
        std::string s("file://etc/motd");
        Uri u = Uri::parse(s);
        EXPECT_EQ("file", u.getScheme());
        EXPECT_EQ("", u.getUserName());
        EXPECT_EQ("", u.getPassword());
        EXPECT_EQ("etc", u.getHost());
        EXPECT_EQ(0, u.getPort());
        EXPECT_EQ("etc", u.getAuthority());
        EXPECT_EQ("/motd", u.getPath());
        EXPECT_EQ("", u.getQuery());
        EXPECT_EQ("", u.getFragment());
        EXPECT_EQ(s, u.toString());
    }

    {
        // test query parameters
        std::string s("http://localhost?&key1=foo&key2=&key3&=bar&=bar=&");
        Uri u           = Uri::parse(s);
        auto paramsList = u.getQueryParams();
        std::map<std::string, std::string> params;
        for (auto&&param : paramsList)
        {
            params[param.first] = param.second;
        }
        EXPECT_EQ(3, params.size());
        EXPECT_EQ("foo", params["key1"]);
        EXPECT_NE(params.end(), params.find("key2"));
        EXPECT_EQ("", params["key2"]);
        EXPECT_NE(params.end(), params.find("key3"));
        EXPECT_EQ("", params["key3"]);
    }

    {
        // test query parameters
        std::string s("http://localhost?&&&&&&&&&&&&&&&");
        Uri u       = Uri::parse(s);
        auto params = u.getQueryParams();
        EXPECT_TRUE(params.empty());
    }

    {
        // test query parameters
        std::string s("http://localhost?&=invalid_key&key2&key3=foo");
        Uri u           = Uri::parse(s);
        auto paramsList = u.getQueryParams();
        std::map<std::string, std::string> params;
        for (auto&&param : paramsList)
        {
            params[param.first] = param.second;
        }
        EXPECT_EQ(2, params.size());
        EXPECT_NE(params.end(), params.find("key2"));
        EXPECT_EQ("", params["key2"]);
        EXPECT_EQ("foo", params["key3"]);
    }

    {
        // test query parameters
        std::string s("http://localhost?&key1=====&&=key2&key3=");
        Uri u           = Uri::parse(s);
        auto paramsList = u.getQueryParams();
        std::map<std::string, std::string> params;
        for (auto&&param : paramsList)
        {
            params[param.first] = param.second;
        }
        EXPECT_EQ(1, params.size());
        EXPECT_NE(params.end(), params.find("key3"));
        EXPECT_EQ("", params["key3"]);
    }

    {
        // test query parameters
        std::string s("ws://localhost:90?key1=foo=bar&key2=foobar&");
        Uri u           = Uri::parse(s);
        auto paramsList = u.getQueryParams();
        std::map<std::string, std::string> params;
        for (auto&& param : paramsList)
        {
            params[param.first] = param.second;
        }
        EXPECT_EQ(1, params.size());
        EXPECT_EQ("foobar", params["key2"]);

        // copy constructor
        {
            Uri v(u);
            u = v = u;
            EXPECT_TRUE(v.isValid());
            EXPECT_EQ("ws", v.getScheme());
            EXPECT_EQ("localhost", v.getHost());
            EXPECT_EQ("localhost", v.getHostName());
            EXPECT_EQ("/", v.getPath());
            EXPECT_EQ(90, v.getPort());
            EXPECT_EQ("", v.getFragment());
            EXPECT_EQ("key1=foo=bar&key2=foobar&", v.getQuery());
            EXPECT_EQ(u, v);
        }

        // copy assign operator
        {
            Uri v;
            v = u;
            EXPECT_TRUE(v.isValid());
            EXPECT_EQ("ws", v.getScheme());
            EXPECT_EQ("localhost", v.getHost());
            EXPECT_EQ("localhost", v.getHostName());
            EXPECT_EQ("/", v.getPath());
            EXPECT_EQ(90, v.getPort());
            EXPECT_EQ("", v.getFragment());
            EXPECT_EQ("key1=foo=bar&key2=foobar&", v.getQuery());
            EXPECT_EQ(u, v);
        }

        // Self move assignment
        {
            u = u;
            EXPECT_TRUE(u.isValid());
        }

        // Self move assignment
        {
            u = std::move(u);
            EXPECT_TRUE(u.isValid());
        }

        // move constructor
        {
            Uri v = std::move(u);
            EXPECT_FALSE(u.isValid());
            EXPECT_TRUE(v.isValid());
            EXPECT_EQ("ws", v.getScheme());
            EXPECT_EQ("localhost", v.getHost());
            EXPECT_EQ("localhost", v.getHostName());
            EXPECT_EQ("/", v.getPath());
            EXPECT_EQ(90, v.getPort());
            EXPECT_EQ("", v.getFragment());
            EXPECT_EQ("key1=foo=bar&key2=foobar&", v.getQuery());
            u = std::move(v);
        }

        // copy assign operator
        {
            Uri v;
            v = std::move(u);
            EXPECT_FALSE(u.isValid());
            EXPECT_TRUE(v.isValid());
            EXPECT_EQ("ws", v.getScheme());
            EXPECT_EQ("localhost", v.getHost());
            EXPECT_EQ("localhost", v.getHostName());
            EXPECT_EQ("/", v.getPath());
            EXPECT_EQ(90, v.getPort());
            EXPECT_EQ("", v.getFragment());
            EXPECT_EQ("key1=foo=bar&key2=foobar&", v.getQuery());
            u = v;
        }
    }

    {
        std::string s("2http://www.facebook.com");

        Uri u = Uri::parse(s);
        EXPECT_FALSE(u.isValid());
    }

    {
        std::string s("www[facebook]com");

        Uri u = Uri::parse("http://" + s);
        EXPECT_FALSE(u.isValid());
    }

    {
        std::string s("http://[::1:8080/hello/world?query#fragment");
        Uri u = Uri::parse(s);
        EXPECT_FALSE(u.isValid());
    }

    {
        std::string s("http://::1]:8080/hello/world?query#fragment");

        Uri u = Uri::parse(s);
        EXPECT_FALSE(u.isValid());
    }

    {
        std::string s("http://::1:8080/hello/world?query#fragment");
        Uri u = Uri::parse(s);
        EXPECT_FALSE(u.isValid());
    }

    {
        std::string s("http://2401:db00:20:7004:face:0:29:0/hello/world?query");
        Uri u = Uri::parse(s);
        EXPECT_FALSE(u.isValid());
    }

    {
        Uri http       = Uri::parse("http://google.com");
        Uri https      = Uri::parse("https://www.google.com:90");
        Uri query      = Uri::parse("http://google.com:8080/foo/bar?foo=bar");
        Uri localhost  = Uri::parse("http://localhost:8080");
        Uri ipv6       = Uri::parse("https://[2001:0db8:85a3:0042:1000:8a2e:0370:7334]");
        Uri ipv6short  = Uri::parse("http://[2001:db8:85a3:42:1000:8a2e:370:7334]");
        Uri ipv6port   = Uri::parse("http://[2001:db8:85a3:42:1000:8a2e:370:7334]:90");
        Uri ipv6abbrev = Uri::parse("http://[2001::7334:a:90]");
        Uri ipv6http   = Uri::parse("http://[2001::7334:a]:90");
        Uri ipv6query  = Uri::parse("http://[2001::7334:a]:90/foo/bar?foo=bar");

        EXPECT_EQ(http.getScheme(), "http");
        EXPECT_EQ(http.getPort(), 80);
        EXPECT_EQ(http.getHost(), "google.com");
        EXPECT_EQ(https.getScheme(), "https");
        EXPECT_EQ(https.getPort(), 90);
        EXPECT_EQ(https.getHost(), "www.google.com");
        EXPECT_EQ(query.getPort(), 8080);
        EXPECT_EQ(query.getPathEtc(), "/foo/bar?foo=bar");
        EXPECT_EQ(localhost.getScheme(), "http");
        EXPECT_EQ(localhost.getHost(), "localhost");
        EXPECT_EQ(localhost.getPort(), 8080);
        EXPECT_EQ(ipv6.getScheme(), "https");
        EXPECT_EQ(ipv6.getHostName(), "2001:0db8:85a3:0042:1000:8a2e:0370:7334");
        EXPECT_EQ(ipv6.getPort(), 443);
        EXPECT_EQ(ipv6short.getScheme(), "http");
        EXPECT_EQ(ipv6short.getHostName(), "2001:db8:85a3:42:1000:8a2e:370:7334");
        EXPECT_EQ(ipv6short.getPort(), 80);
        EXPECT_EQ(ipv6port.getScheme(), "http");
        EXPECT_EQ(ipv6port.getHostName(), "2001:db8:85a3:42:1000:8a2e:370:7334");
        EXPECT_EQ(ipv6port.getPort(), 90);
        EXPECT_EQ(ipv6abbrev.getScheme(), "http");
        EXPECT_EQ(ipv6abbrev.getHostName(), "2001::7334:a:90");
        EXPECT_EQ(ipv6abbrev.getPort(), 80);
        EXPECT_EQ(ipv6http.getScheme(), "http");
        EXPECT_EQ(ipv6http.getPort(), 90);
        EXPECT_EQ(ipv6http.getHostName(), "2001::7334:a");
        EXPECT_EQ(ipv6query.getScheme(), "http");
        EXPECT_EQ(ipv6query.getPort(), 90);
        EXPECT_EQ(ipv6query.getHostName(), "2001::7334:a");
        EXPECT_EQ(ipv6query.getPathEtc(), "/foo/bar?foo=bar");
    }

    {
        Uri u0 = Uri::parse("http://localhost:84/foo.html?&q=123");
        Uri u1 = Uri::parse("https://localhost:82/foo.html?&q=1");
        Uri u2 = Uri::parse("ws://localhost/foo");
        Uri u3 = Uri::parse("localhost/foo");
        Uri u4 = Uri::parse("localhost:8080");
        Uri u5 = Uri::parse("bb://localhost?&foo=12:4&ccc=13");
        Uri u6 = Uri::parse("cc://localhost:91?&foo=321&bbb=1");

        EXPECT_EQ(u0.getScheme(), "http");
        EXPECT_EQ(u0.getHost(), "localhost");
        EXPECT_EQ(u0.getPort(), 84);
        EXPECT_EQ(u0.getPath(), "/foo.html");
        EXPECT_EQ(u0.getPathEtc(), "/foo.html?&q=123");

        EXPECT_EQ(u1.getScheme(), "https");
        EXPECT_EQ(u1.getHost(), "localhost");
        EXPECT_EQ(u1.getPort(), 82);
        EXPECT_EQ(u1.getPathEtc(), "/foo.html?&q=1");

        EXPECT_EQ(u2.getScheme(), "ws");
        EXPECT_EQ(u2.getHost(), "localhost");
        EXPECT_EQ(u2.getPort(), 80);
        EXPECT_EQ(u2.getPath(), "/foo");

        EXPECT_EQ(u3.getScheme(), "");
        EXPECT_EQ(u3.getHost(), "localhost");
        EXPECT_EQ(u3.getPort(), 0);
        EXPECT_EQ(u3.getPath(), "/foo");

        EXPECT_EQ(u4.getScheme(), "");
        EXPECT_EQ(u4.getHost(), "localhost");
        EXPECT_EQ(u4.getPort(), 8080);
        EXPECT_EQ(u4.getPath(), "/");
        EXPECT_EQ(u4.getPathEtc(), "/");

        EXPECT_EQ(u5.getScheme(), "bb");
        EXPECT_EQ(u5.getHost(), "localhost");
        EXPECT_EQ(u5.getPort(), 0);
        EXPECT_EQ(u5.getPath(), "/");
        EXPECT_EQ(u5.getPathEtc(), "/?&foo=12:4&ccc=13");
        EXPECT_EQ(u5.getQuery(), "&foo=12:4&ccc=13");

        EXPECT_EQ(u6.getScheme(), "cc");
        EXPECT_EQ(u6.getHost(), "localhost");
        EXPECT_EQ(u6.getPort(), 91);
        EXPECT_EQ(u6.getPath(), "/");
        EXPECT_EQ(u6.getPathEtc(), "/?&foo=321&bbb=1");
        EXPECT_EQ(u6.getQuery(), "&foo=321&bbb=1");
    }
}

std::string ParseUriTest::subtitle() const
{
    return "Uri::parse Test";
}

// MathUtilTest

namespace UnitTest
{

#ifdef INCLUDE_NEON32
#    include "math/MathUtilNeon.inl"
#endif

#ifdef INCLUDE_NEON64
#    include "math/MathUtilNeon64.inl"
#endif

#ifdef INCLUDE_SSE
// FIXME: #include "math/MathUtilSSE.inl"
#endif

#include "math/MathUtil.inl"

}  // namespace UnitTest

// I know the next line looks ugly, but it's a way to test MathUtil. :)
using namespace UnitTest::ax;

static void __checkMathUtilResult(const char* description, const float* a1, const float* a2, int size)
{
    ax::print("-------------checking %s ----------------------------", description);
    // Check whether the result of the optimized instruction is the same as which is implemented in C
    for (int i = 0; i < size; ++i)
    {
        bool r = fabs(a1[i] - a2[i]) < 0.00001f;  // FLT_EPSILON;
        if (r)
        {
            ax::print("Correct: a1[%d]=%f, a2[%d]=%f", i, a1[i], i, a2[i]);
        }
        else
        {
            ax::print("Wrong: a1[%d]=%f, a2[%d]=%f", i, a1[i], i, a2[i]);
        }
        AXASSERT(r, "The optimized instruction is implemented in a wrong way, please check it!");
    }
}

void MathUtilTest::onEnter()
{
    UnitTestDemo::onEnter();

    const int MAT4_SIZE = 16;
    const int VEC4_SIZE = 4;

    const float inMat41[MAT4_SIZE] = {
        0.234023f, 2.472349f, 1.984244f, 2.23348f, 0.634124f, 0.234975f, 6.384572f, 0.82368f,
        0.738028f, 1.845237f, 1.934721f, 1.62343f, 0.339023f, 3.472452f, 1.324714f, 4.23852f,
    };

    const float inMat42[MAT4_SIZE] = {
        1.640232f, 4.472349f, 0.983244f, 1.23343f, 2.834124f, 8.234975f, 0.082572f, 3.82464f,
        3.238028f, 2.845237f, 0.331721f, 4.62544f, 4.539023f, 9.472452f, 3.520714f, 2.23252f,
    };

    const float scalar = 1.323298f;
    const float x      = 0.432234f;
    const float y      = 1.333229f;
    const float z      = 2.535292f;
    const float w      = 4.632234f;

    const float inVec4[VEC4_SIZE]  = {2.323478f, 0.238482f, 4.223783f, 7.238238f};
    const float inVec42[VEC4_SIZE] = {0.322374f, 8.258883f, 3.293683f, 2.838337f};

    float outMat4Opt[MAT4_SIZE] = {0};
    float outMat4C[MAT4_SIZE]   = {0};
    float outVec4Opt[VEC4_SIZE] = {0};
    float outVec4C[VEC4_SIZE]   = {0};

    // inline static void addMatrix(const float* m, float scalar, float* dst);
    MathUtilC::addMatrix(inMat41, scalar, outMat4C);

#ifdef INCLUDE_NEON32
    MathUtilNeon::addMatrix(inMat41, scalar, outMat4Opt);
#endif

#ifdef INCLUDE_NEON64
    MathUtilNeon64::addMatrix(inMat41, scalar, outMat4Opt);
#endif

#ifdef INCLUDE_SSE
// FIXME:
#endif

    __checkMathUtilResult("inline static void addMatrix(const float* m, float scalar, float* dst);", outMat4C,
                          outMat4Opt, MAT4_SIZE);
    // Clean
    memset(outMat4C, 0, sizeof(outMat4C));
    memset(outMat4Opt, 0, sizeof(outMat4Opt));

    // inline static void addMatrix(const float* m1, const float* m2, float* dst);
    MathUtilC::addMatrix(inMat41, inMat42, outMat4C);

#ifdef INCLUDE_NEON32
    MathUtilNeon::addMatrix(inMat41, inMat42, outMat4Opt);
#endif

#ifdef INCLUDE_NEON64
    MathUtilNeon64::addMatrix(inMat41, inMat42, outMat4Opt);
#endif

#ifdef INCLUDE_SSE
    // FIXME:
#endif

    __checkMathUtilResult("inline static void addMatrix(const float* m1, const float* m2, float* dst);", outMat4C,
                          outMat4Opt, MAT4_SIZE);
    // Clean
    memset(outMat4C, 0, sizeof(outMat4C));
    memset(outMat4Opt, 0, sizeof(outMat4Opt));

    // inline static void subtractMatrix(const float* m1, const float* m2, float* dst);
    MathUtilC::subtractMatrix(inMat41, inMat42, outMat4C);

#ifdef INCLUDE_NEON32
    MathUtilNeon::subtractMatrix(inMat41, inMat42, outMat4Opt);
#endif

#ifdef INCLUDE_NEON64
    MathUtilNeon64::subtractMatrix(inMat41, inMat42, outMat4Opt);
#endif

#ifdef INCLUDE_SSE
    // FIXME:
#endif

    __checkMathUtilResult("inline static void subtractMatrix(const float* m1, const float* m2, float* dst);", outMat4C,
                          outMat4Opt, MAT4_SIZE);
    // Clean
    memset(outMat4C, 0, sizeof(outMat4C));
    memset(outMat4Opt, 0, sizeof(outMat4Opt));

    // inline static void multiplyMatrix(const float* m, float scalar, float* dst);
    MathUtilC::multiplyMatrix(inMat41, scalar, outMat4C);

#ifdef INCLUDE_NEON32
    MathUtilNeon::multiplyMatrix(inMat41, scalar, outMat4Opt);
#endif

#ifdef INCLUDE_NEON64
    MathUtilNeon64::multiplyMatrix(inMat41, scalar, outMat4Opt);
#endif

#ifdef INCLUDE_SSE
    // FIXME:
#endif

    __checkMathUtilResult("inline static void multiplyMatrix(const float* m, float scalar, float* dst);", outMat4C,
                          outMat4Opt, MAT4_SIZE);
    // Clean
    memset(outMat4C, 0, sizeof(outMat4C));
    memset(outMat4Opt, 0, sizeof(outMat4Opt));

    // inline static void multiplyMatrix(const float* m1, const float* m2, float* dst);
    MathUtilC::multiplyMatrix(inMat41, inMat42, outMat4C);

#ifdef INCLUDE_NEON32
    MathUtilNeon::multiplyMatrix(inMat41, inMat42, outMat4Opt);
#endif

#ifdef INCLUDE_NEON64
    MathUtilNeon64::multiplyMatrix(inMat41, inMat42, outMat4Opt);
#endif

#ifdef INCLUDE_SSE
    // FIXME:
#endif

    __checkMathUtilResult("inline static void multiplyMatrix(const float* m1, const float* m2, float* dst);", outMat4C,
                          outMat4Opt, MAT4_SIZE);
    // Clean
    memset(outMat4C, 0, sizeof(outMat4C));
    memset(outMat4Opt, 0, sizeof(outMat4Opt));

    // inline static void negateMatrix(const float* m, float* dst);
    MathUtilC::negateMatrix(inMat41, outMat4C);

#ifdef INCLUDE_NEON32
    MathUtilNeon::negateMatrix(inMat41, outMat4Opt);
#endif

#ifdef INCLUDE_NEON64
    MathUtilNeon64::negateMatrix(inMat41, outMat4Opt);
#endif

#ifdef INCLUDE_SSE
    // FIXME:
#endif

    __checkMathUtilResult("inline static void negateMatrix(const float* m, float* dst);", outMat4C, outMat4Opt,
                          MAT4_SIZE);
    // Clean
    memset(outMat4C, 0, sizeof(outMat4C));
    memset(outMat4Opt, 0, sizeof(outMat4Opt));

    // inline static void transposeMatrix(const float* m, float* dst);
    MathUtilC::transposeMatrix(inMat41, outMat4C);

#ifdef INCLUDE_NEON32
    MathUtilNeon::transposeMatrix(inMat41, outMat4Opt);
#endif

#ifdef INCLUDE_NEON64
    MathUtilNeon64::transposeMatrix(inMat41, outMat4Opt);
#endif

#ifdef INCLUDE_SSE
    // FIXME:
#endif

    __checkMathUtilResult("inline static void transposeMatrix(const float* m, float* dst);", outMat4C, outMat4Opt,
                          MAT4_SIZE);
    // Clean
    memset(outMat4C, 0, sizeof(outMat4C));
    memset(outMat4Opt, 0, sizeof(outMat4Opt));

    // inline static void transformVec4(const float* m, float x, float y, float z, float w, float* dst);
    MathUtilC::transformVec4(inMat41, x, y, z, w, outVec4C);

#ifdef INCLUDE_NEON32
    MathUtilNeon::transformVec4(inMat41, x, y, z, w, outVec4Opt);
#endif

#ifdef INCLUDE_NEON64
    MathUtilNeon64::transformVec4(inMat41, x, y, z, w, outVec4Opt);
#endif

#ifdef INCLUDE_SSE
    // FIXME:
#endif

    __checkMathUtilResult(
        "inline static void transformVec4(const float* m, float x, float y, float z, float w, float* dst);", outVec4C,
        outVec4Opt, VEC4_SIZE);
    // Clean
    memset(outVec4C, 0, sizeof(outVec4C));
    memset(outVec4Opt, 0, sizeof(outVec4Opt));

    // inline static void transformVec4(const float* m, const float* v, float* dst);
    MathUtilC::transformVec4(inMat41, inVec4, outVec4C);

#ifdef INCLUDE_NEON32
    MathUtilNeon::transformVec4(inMat41, inVec4, outVec4Opt);
#endif

#ifdef INCLUDE_NEON64
    MathUtilNeon64::transformVec4(inMat41, inVec4, outVec4Opt);
#endif

#ifdef INCLUDE_SSE
    // FIXME:
#endif

    __checkMathUtilResult("inline static void transformVec4(const float* m, const float* v, float* dst);", outVec4C,
                          outVec4Opt, VEC4_SIZE);
    // Clean
    memset(outVec4C, 0, sizeof(outVec4C));
    memset(outVec4Opt, 0, sizeof(outVec4Opt));

    // inline static void crossVec3(const float* v1, const float* v2, float* dst);
    MathUtilC::crossVec3(inVec4, inVec42, outVec4C);

#ifdef INCLUDE_NEON32
    MathUtilNeon::crossVec3(inVec4, inVec42, outVec4Opt);
#endif

#ifdef INCLUDE_NEON64
    MathUtilNeon64::crossVec3(inVec4, inVec42, outVec4Opt);
#endif

#ifdef INCLUDE_SSE
    // FIXME:
#endif

    __checkMathUtilResult("inline static void crossVec3(const float* v1, const float* v2, float* dst);", outVec4C,
                          outVec4Opt, VEC4_SIZE);
    // Clean
    memset(outVec4C, 0, sizeof(outVec4C));
    memset(outVec4Opt, 0, sizeof(outVec4Opt));
}

std::string MathUtilTest::subtitle() const
{
    return "MathUtilTest";
}

// ResizableBufferAdapterTest

void ResizableBufferAdapterTest::onEnter()
{
    UnitTestDemo::onEnter();

    
    yasio::byte_buffer buffer;

    FileUtils::getInstance()->getContents("effect1.wav", &buffer);
    EXPECT_EQ(buffer.size(), 10026);

    FileUtils::getInstance()->getContents("effect2.ogg", &buffer);
    EXPECT_EQ(buffer.size(), 4278);

    FileUtils::getInstance()->getContents("effect1.wav", &buffer);
    EXPECT_EQ(buffer.size(), 10026);
}

std::string ResizableBufferAdapterTest::subtitle() const
{
    return "ResiziableBufferAdapter<yasio::byte_buffer> Test";
}
